from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.contrib.sites.models import Site
from django.forms import model_to_dict
from django.shortcuts import render_to_response
from django.template import RequestContext

from piston.handler import BaseHandler, AnonymousBaseHandler
from piston.utils import rc, validate, throttle
from piston.forms import ModelForm
from piston.doc import generate_doc

from projects.models import Project, Release
from projects.forms import ProjectCreationForm, ProjectEditForm
from projects.forms import ReleaseCreationForm, ReleaseEditForm

class GenericDisplay:
    @classmethod
    def url(self, object):
        return "http://%s%s" % (Site.objects.get_current(),
                                object.get_absolute_url())
    
    @classmethod
    def markup(self, object):
        return object.get_markup_display()
    
    @classmethod
    def creator(self, object):
        return object.creator.username
    
    @classmethod
    def last_editor(self, object):
        return object.last_editor.username
    
    
class ProjectDisplay(GenericDisplay):    
    @classmethod
    def members(self, project):
        return ", ".join([member.username for member in project.members.all()])
    
    @classmethod
    def admins(self, project):
        return ", ".join([admin.username for admin in project.admins.all()])
    
    @classmethod
    def comments(self, project):
        return project.comment_count
    
    @classmethod
    def newest_version(self, project):
        if project.release_set.count():
            return project.release_set.all()[0].version
        else:
            return "no release yet"
    
class ReleaseDisplay(GenericDisplay):
    @classmethod
    def project(self, release):
        return release.project.name
    

class AnonymousProjectHandler(ProjectDisplay, AnonymousBaseHandler):
    model = Project
    allowed_methods = ["GET"]
    fields = ['name', 'summary', 'description', 
              #'raw_description', 'raw_summary', 'slug',
              
              'is_inactive', 'hide', 'created', 'last_edited',
              'comments', 'markup', 'admins',
              'members', 'url', 'homepage_link', 
              'creator', 'last_editor', 'newest_version']
    
    def read(self, request, slug):              
        """
        get a project
        """
        try:
            p = Project.objects.get(slug=slug, is_inactive=False)
        except ObjectDoesNotExist:
            return rc.NOT_FOUND
        except MultipleObjectsReturned: # should never happen, since we're using a PK
            return rc.BAD_REQUEST
        else:
            return p              

class ProjectHandler(ProjectDisplay, BaseHandler):
    allowed_methods = ['GET', 'PUT', 'POST', 'DELETE']
    model = Project
    anonymous = AnonymousProjectHandler
    fields = ['name', 'summary', 'description', 
              #'raw_description', 'raw_summary', 'slug',
              
              'is_inactive', 'hide', 'created', 'last_edited',
              'comments', 'markup', 'admins',
              'members', 'url', 'homepage_link', 
              'creator', 'last_editor', 'newest_version']
    
    def read(self, request, slug):
        """
        get a project
        """
        return self.anonymous.read(request, slug)
    
    
    def update(self, request, slug):
        "update a project"
        p = Project.objects.filter(slug=slug)
        if not p:
            return rc.NOT_FOUND
        elif len(p) > 1:
            return rc.BAD_REQUEST
        elif "has_admin_perm" in p[0].get_perms(request.user):
            p = p[0]
            initial = model_to_dict(p)
            data = request.PUT
            data._mutable = True
            if "admins" not in data:
                data["admins"] = ",".join([a.username for a in p.admins.all()])
            if "members" not in data:
                data["members"] = ",".join([m.username for m in p.members.all()])
            filt = lambda v: v not in data.keys()
            for field in filter(filt, [f.name for f in p._meta.fields]):
                     data[field] = initial.get(field, None)
            form = ProjectEditForm(data=data, instance=p)
            if form.is_valid():
                p = form.save()
                return p
            return rc.BAD_REQUEST
        else:
            return rc.FORBIDDEN
    
    # users may create 5 projects within 10 minutes
    @throttle(5, 10*60)
    def create(self, request, slug=None):
        "create a new project"
        form = ProjectCreationForm(data=request.POST)
        if form.is_valid():
            project = form.save(commit=False)
            project.creator = request.user
            project.last_editor = request.user
            project.save()
            if not request.user in project.admins.all():
                project.admins.add(request.user)
                project.save()
            return project
        return rc.BAD_REQUEST
        
    def delete(self, request, slug):
        "mark a project inactive (you cannot delete it completely)"
        p = Project.objects.filter(slug=slug)
        if not p:
            return rc.NOT_FOUND
        elif len(p) > 1:
            return rc.BAD_REQUEST
        elif "has_admin_perm" in p[0].get_perms(request.user):
            p[0].is_inactive = True
            # it was not deleted but set inactive, so do not return rc.DELETED
            return rc.ALL_OK
        else:
            return rc.FORBIDDEN        
        
        
####################

class AnonymousReleaseHandler(ReleaseDisplay, AnonymousBaseHandler):
    model = Release
    allowed_methods = ["GET"]
    fields = ['project', 'version', 'markup', 'summary', 
    #'raw_summary',  'raw_changelog', 
              'changelog', 'created', 'last_edited', 'creator',
              'last_editor', 'notes_url', 'download_url', 'url'
               ]
               
    def read(self, request, project_slug, slug):
        """
        get a release
        """
        try:
            r = Release.objects.get(slug=slug, project__slug=project_slug,
                                    project__is_inactive=False)
        except ObjectDoesNotExist:
            if slug == "latest":
                q = Release.objects.filter(project__slug=project_slug,
                                           project__is_inactive=False)
                if q.count():
                    return q[0]
            return rc.NOT_FOUND            
        except MultipleObjectsReturned: # should never happen, since we're using a PK
            return rc.BAD_REQUEST
        else:
            return r


class ReleaseHandler(ReleaseDisplay, BaseHandler):
    allowed_methods = ['GET', 'PUT', 'POST', 'DELETE']
    model = Release
    anonymous = AnonymousReleaseHandler
    fields = AnonymousReleaseHandler.fields
    #fields = ['project', 'version', 'markup', 'summary', 
    #'raw_summary',  'raw_changelog', 
     #         'changelog', 'created', 'last_edited', 'creator',
     #         'last_editor', 'notes_url', 'download_url', 'url'
     #          ]
    
    def read(self, request, project_slug, slug):
        """
        get a release
        """
        return self.anonymous().read(request, project_slug, slug)
        
        
    
    def update(self, request, project_slug, slug):
        "update a release"
        r = self.read(request, project_slug, slug)
        if not isinstance(r, Release):
            return r
        
        elif "has_member_perm" in r.project.get_perms(request.user):
            initial = model_to_dict(r)
            data = request.PUT
            data._mutable = True
            filt = lambda v: v not in data.keys()
            for field in filter(filt, [f.name for f in r._meta.fields]):
                     data[field] = initial.get(field, None)
            form = ReleaseEditForm(data=data, instance=r)
            if form.is_valid():
                r = form.save()
                return r
            return rc.BAD_REQUEST
        else:
            return rc.FORBIDDEN
    
#    # users may create 5 projects within 10 minutes
    @throttle(5, 10*60)
    def create(self, request, project_slug, slug=None):
        "release a new version of a project"
        try:
            project = Project.objects.get(slug=project_slug, is_inactive=False)
        except ObjectDoesNotExist:
            return rc.NOT_FOUND            
        except MultipleObjectsReturned: # should never happen, since we're using a PK
            return rc.BAD_REQUEST
        if "has_member_perm" in project.get_perms(request.user):
            instance = Release(creator=request.user, 
                                     last_editor=request.user,
                                     project=project)
            form = ReleaseCreationForm(data=request.POST, instance=instance)
            
            if form.is_valid():
                release = form.save()
                return release
        
            return rc.BAD_REQUEST
        else:
            return rc.FORBIDDEN
 
        
    def delete(self, request, project_slug, slug):
        """"delete a release"""
        r = self.read(request, project_slug, slug)
        if not isinstance(r, Release):
            return r
        elif "has_member_perm" in r.project.get_perms(request.user):
            r.delete()
            return rc.DELETED
        else:
            return rc.FORBIDDEN        

####################        
        
def generate_documentation(request):
    docs = []
    for handler in (ProjectHandler,ReleaseHandler):
        doc = generate_doc(handler)
        docdic = {"name": doc.name,
                  "model": doc.get_model(),
                 # "uri": doc.resource_uri_template(),
                  "methods": []}
        
        methods = doc.get_methods()
        
        for method in methods:
           #print dir(method)
           methoddic = {"name": method.name,
                        "sig1": method.signature,
                        "doc": method.get_doc()}
           sig = ''
           for argn, argdef in method.iter_args():
              sig += argn
              if argdef:
                 sig += "=%s" % argdef
              sig += ', '
           sig = sig.rstrip(", ")
           methoddic["sig2"] = sig
           docdic["methods"].append(methoddic)
        docs.append(docdic)
    return render_to_response('api/doc.html', {"docs":docs},
                              context_instance=RequestContext(request))
